/**
 * Copyright 2020 Huawei Technologies Co., Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "util.h"

#include <dlfcn.h>
#include <regex.h>
#include <stdlib.h>
#include <sys/time.h>

#include <algorithm>
#include <climits>
#include <fstream>

#include "constant.h"

using namespace std;
using namespace ge;

namespace {
// max file size limit
constexpr int kMaxFileSizeLimit = INT_MAX;
// data type map
const map<string, DataType> data_type_map = {
      {"DT_BOOL", DataType::DT_BOOL},
      {"DT_INT8", DataType::DT_INT8},
      {"DT_UINT8", DataType::DT_UINT8},
      {"DT_INT16", DataType::DT_INT16},
      {"DT_UINT16", DataType::DT_UINT16},
      {"DT_INT32", DataType::DT_INT32},
      {"DT_UINT32", DataType::DT_UINT32},
      {"DT_INT64", DataType::DT_INT64},
      {"DT_UINT64", DataType::DT_UINT64},
      {"DT_FLOAT16", DataType::DT_FLOAT16},
      {"DT_FLOAT", DataType::DT_FLOAT},
      {"DT_DOUBLE", DataType::DT_DOUBLE},
      {"DT_COMPLEX64", DataType::DT_COMPLEX64},
      {"DT_COMPLEX128", DataType::DT_COMPLEX128},
      {"DT_QINT8", DataType::DT_QINT8},
      {"DT_QUINT8", DataType::DT_QUINT8},
      {"DT_QINT16", DataType::DT_QINT16},
      {"DT_QUINT16", DataType::DT_QUINT16},
      {"DT_QINT32", DataType::DT_QINT32},
      {"DT_STRING", DataType::DT_STRING},
      {"DT_STRING_REF", DataType::DT_STRING_REF},
      {"DT_RESOURCE", DataType::DT_RESOURCE},
      {"DT_VARIANT", DataType::DT_VARIANT}};
}  // namespace

namespace aicpu {
const string GetSoPath(const void *instance) {
  Dl_info dl_info;
  char resoved_path[PATH_MAX] = {0x00};
  string real_file_path;
  AICPU_IF_BOOL_EXEC((dladdr(instance, &dl_info) == 0),
      AICPU_REPORT_INNER_ERROR("Call dladdr failed.");
      return real_file_path);
  string so_path = dl_info.dli_fname;
  AICPUE_LOGI("So file path=%s", so_path.c_str());
  AICPU_IF_BOOL_EXEC(so_path.empty(), AICPUE_LOGI("So file path is empty.");
                     return real_file_path);
  AICPU_IF_BOOL_EXEC(realpath(so_path.c_str(), resoved_path) == nullptr,
      AICPU_REPORT_INNER_ERROR("realpath [%s] failed, %s.",
          so_path.c_str(), strerror(errno));
      return real_file_path);
  string so_file_path = resoved_path;
  string::size_type pos = so_file_path.rfind('/');
  AICPU_IF_BOOL_EXEC(
      pos == string::npos,
      AICPU_REPORT_INNER_ERROR("Invalid path[%s] not contain /.",
          so_file_path.c_str());
      return real_file_path);
  real_file_path = so_file_path.substr(0, pos + 1);

  AICPUE_LOGI("Real config File path is %s", real_file_path.c_str());
  return real_file_path;
}

string CurrentTimeInStr() {
  time_t now = time(nullptr);
  tm *ptm = localtime(&now);
  if (ptm == nullptr) {
    AICPU_REPORT_INNER_ERROR("Call localtime function failed.");
    return "";
  }

  const int time_buffer_len = 32;
  char buffer[time_buffer_len] = {0};
  // format: 20171122042550
  strftime(buffer, time_buffer_len, "%Y%m%d%H%M%S", ptm);
  return string(buffer);
}

aicpu::State ReadJsonFile(const string &file_path, nlohmann::json &json_read) {
  AICPUE_LOGI("Read %s json file", file_path.c_str());
  ifstream ifs(file_path);
  AICPU_IF_BOOL_EXEC(
      !ifs.is_open(),
      return aicpu::State(ge::FAILED, Stringcat("open [", file_path, "] failed")));

  try {
    ifs >> json_read;
    ifs.close();
  } catch (const nlohmann::json::exception &e) {
    AICPU_IF_BOOL_EXEC(ifs.is_open(), ifs.close();)
    return aicpu::State(ge::FAILED, e.what());
  }

  AICPUE_LOGD("Read %s json file, content is: %s.", file_path.c_str(),
              json_read.dump().c_str());
  return aicpu::State(ge::SUCCESS);
}

aicpu::State StringToBool(const string &str, bool &result) {
  result = false;
  string buff = str;
  try {
    transform(buff.begin(), buff.end(), buff.begin(), ::tolower);
    if ((buff == "false") || (buff == "true")) {
      istringstream(buff) >> boolalpha >> result;
      return aicpu::State(ge::SUCCESS);
    }
  } catch (std::exception &e) {
    // something else reason
    return aicpu::State(ge::FAILED, e.what());
  }
  return aicpu::State(ge::FAILED, "unknown reason");
}

void SplitSequence(const string &str, const string &pattern,
                   set<string> &result) {
  // Easy to intercept the last piece of data
  string strs = str + pattern;

  size_t pos = strs.find(pattern);
  size_t size = strs.size();

  while (pos != string::npos) {
    string x = strs.substr(0, pos);
    if (!x.empty()) {
      result.emplace(x);
    }
    strs = strs.substr(pos + pattern.length(), size);
    pos = strs.find(pattern);
  }
}

void SplitSequence(const string &str, const string &pattern,
                   vector<string> &result) {
  // Easy to intercept the last piece of data
  string strs = str + pattern;

  size_t pos = strs.find(pattern);
  size_t size = strs.size();

  while (pos != string::npos) {
    string x = strs.substr(0, pos);
    if (!x.empty()) {
      result.push_back(x);
    }
    strs = strs.substr(pos + pattern.length(), size);
    pos = strs.find(pattern);
  }
}

void GetDataType(const map<string, string> &data_types,
                 const string &data_type_name, set<DataType> &data_type) {
  auto iter = data_types.find(data_type_name);
  if (iter != data_types.end()) {
    string data_type_str = iter->second;
    set<string> data_type_set;
    SplitSequence(data_type_str, kConfigItemSeparator, data_type_set);

    for (auto &elem : data_type_set) {
      auto it = data_type_map.find(elem);
      // only data_type in configure file exists in data_type_map, save it and
      // return
      if (it != data_type_map.end()) {
        data_type.insert(it->second);
      }
    }
  } else {
    data_type = {DT_UNDEFINED};
  }
}

void GetDataType(const map<string, string> &data_types,
                 const string &data_type_name, vector<DataType> &data_type) {
  auto iter = data_types.find(data_type_name);
  if (iter != data_types.end()) {
    string data_type_str = iter->second;
    vector<string> data_type_set;
    SplitSequence(data_type_str, kConfigItemSeparator, data_type_set);

    for (auto &elem : data_type_set) {
      auto it = data_type_map.find(elem);
      // only data_type in configure file exists in data_type_map, save it and
      // return
      if (it != data_type_map.end()) {
        data_type.push_back(it->second);
      }
    }
  }
}

bool ConvertDataType2String(string &elem, DataType data_type) {
  static const map<DataType, string> data_type_map_convert = {
      {DataType::DT_FLOAT, "DT_FLOAT"},
      {DataType::DT_FLOAT16, "DT_FLOAT16"},
      {DataType::DT_INT8, "DT_INT8"},
      {DataType::DT_UINT8, "DT_UINT8"},
      {DataType::DT_INT16, "DT_INT16"},
      {DataType::DT_UINT16, "DT_UINT16"},
      {DataType::DT_INT32, "DT_INT32"},
      {DataType::DT_INT64, "DT_INT64"},
      {DataType::DT_UINT32, "DT_UINT32"},
      {DataType::DT_UINT64, "DT_UINT64"},
      {DataType::DT_BOOL, "DT_BOOL"},
      {DataType::DT_DOUBLE, "DT_DOUBLE"},
      {DataType::DT_COMPLEX64, "DT_COMPLEX64"},
      {DataType::DT_COMPLEX128, "DT_COMPLEX128"},
      {DataType::DT_STRING, "DT_STRING"},
      {DataType::DT_STRING_REF, "DT_STRING_REF"},
      {DataType::DT_RESOURCE, "DT_RESOURCE"},
      {DataType::DT_QINT8, "DT_QINT8"},
      {DataType::DT_QINT16, "DT_QINT16"},
      {DataType::DT_QINT32, "DT_QINT32"},
      {DataType::DT_QUINT8, "DT_QUINT8"},
      {DataType::DT_QUINT16, "DT_QUINT16"},
      {DataType::DT_DUAL_SUB_INT8, "DT_DUAL_SUB_INT8"},
      {DataType::DT_DUAL_SUB_UINT8, "DT_DUAL_SUB_UINT8"},
      {DataType::DT_DUAL, "DT_DUAL"},
      {DataType::DT_VARIANT, "DT_VARIANT"}};

  auto it = data_type_map_convert.find(data_type);
  // only data_type in configure file exists in data_type_map_convert, save it and return
  if (it != data_type_map_convert.end()) {
    elem = it->second;
    return true;
  }
  elem = "DT_INVALID";
  return false;
}

int32_t GetDataTypeSize(DataType data_type) {
  constexpr uint32_t kSizeOfFloat16 = 2;
  // DT_RESOURCE: ge allocate 8 bytes
  constexpr int32_t kSizeOfResource = 8;
  // DT_STRING_REF: ge allocate 16 bytes
  // store mutex pointer and tensor pointer
  constexpr int32_t kSizeOfStringRef = 16;
  // DT_STRING: ge allocate 8 bytes
  // store string rawdata pointer on device ddr momery
  constexpr int32_t kSizeOfString = 16;
  constexpr int32_t kSizeOfComplex64 = 8;
  constexpr int32_t kSizeOfComplex128 = 16;
  constexpr int32_t kSizeOfVariant = 8;

  static const map<DataType, int32_t> data_type_size_map = {
      {DataType::DT_FLOAT16, kSizeOfFloat16},
      {DataType::DT_FLOAT, sizeof(float)},
      {DataType::DT_DOUBLE, sizeof(double)},
      {DataType::DT_INT8, sizeof(int8_t)},
      {DataType::DT_UINT8, sizeof(uint8_t)},
      {DataType::DT_INT16, sizeof(int16_t)},
      {DataType::DT_UINT16, sizeof(uint16_t)},
      {DataType::DT_INT32, sizeof(int32_t)},
      {DataType::DT_UINT32, sizeof(uint32_t)},
      {DataType::DT_INT64, sizeof(int64_t)},
      {DataType::DT_UINT64, sizeof(uint64_t)},
      {DataType::DT_BOOL, sizeof(bool)},
      {DataType::DT_RESOURCE, kSizeOfResource},
      {DataType::DT_STRING, kSizeOfString},
      {DataType::DT_STRING_REF, kSizeOfStringRef},
      {DataType::DT_COMPLEX64, kSizeOfComplex64},
      {DataType::DT_COMPLEX128, kSizeOfComplex128},
      {DataType::DT_QINT16, sizeof(int16_t)},
      {DataType::DT_QUINT16, sizeof(uint16_t)},
      {DataType::DT_QINT8, sizeof(int8_t)},
      {DataType::DT_QUINT8, sizeof(uint8_t)},
      {DataType::DT_QINT32, sizeof(int32_t)},
      {DataType::DT_VARIANT, kSizeOfVariant}};

  map<DataType, int32_t>::const_iterator iter = data_type_size_map.find(data_type);
  if (iter != data_type_size_map.end()) {
    return iter->second;
  } else {
    return 0;
  }
}

bool CheckInt64MulOverflow(int64_t a, int64_t b) {
  // Not overflow
  if (a == 0) {
    return false;
  }
  if (b <= (LLONG_MAX / a)) {
    return false;
  }
  return true;
}

bool CheckUint64AddOverflow(uint64_t a, uint64_t b) {
  // Not overflow
  if (b <= (ULLONG_MAX - a)) {
    return false;
  }
  return true;
}

bool CheckInt64AddOverflow(int64_t a, int64_t b) {
  // Not overflow
  if (b <= (LLONG_MAX - a)) {
    return false;
  }
  return true;
}

bool CheckUint32AddOverflow(uint32_t a, uint32_t b) {
  // Not overflow
  if (b <= (UINT32_MAX - a)) {
    return false;
  }
  return true;
}

NodePtr GenGeNode(const string &name, const string &type, int in_count,
                  int out_count, Format format, DataType data_type,
                  vector<int64_t> shape) {
  ComputeGraphPtr compute_graph_ptr =
      shared_ptr<ComputeGraph>(new (nothrow) ComputeGraph("ComputeGraph"));
  AICPU_CHECK_NOTNULL_ERRCODE(compute_graph_ptr, nullptr);
  auto tensor_desc = make_shared<GeTensorDesc>();
  tensor_desc->SetShape(GeShape(move(shape)));
  tensor_desc->SetFormat(format);
  tensor_desc->SetDataType(data_type);

  auto op_desc = make_shared<OpDesc>(name, type);
  for (int i = 0; i < in_count; ++i) {
    op_desc->AddInputDesc(tensor_desc->Clone());
  }
  for (int i = 0; i < out_count; ++i) {
    op_desc->AddOutputDesc(tensor_desc->Clone());
  }
  return compute_graph_ptr->AddNode(op_desc);
}

const string RealPath(const string &path) {
  // PATH_MAX is the system marco，indicate the maximum length for file path
  // pclint check，one param in stack can not exceed 1K bytes
  char resoved_path[PATH_MAX] = {0x00};

  std::string res = "";

  // path not exists or not allowed to read，return nullptr
  // path exists and readable, return the resoved path
  if (nullptr != realpath(path.c_str(), resoved_path)) {
    res = resoved_path;
  } else {
    AICPUE_LOGI("Path %s is not exist.", path.c_str());
  }
  return res;
}

bool ReadBytesFromBinaryFile(const std::string &file_name,
                             std::vector<char> &buffer) {
  AICPU_IF_BOOL_EXEC(file_name.empty(),
      AICPU_REPORT_INNER_ERROR("file name is empty.");
      return false)

  std::string real_path = RealPath(file_name);
  AICPU_IF_BOOL_EXEC(real_path.empty(),
      AICPU_REPORT_INNER_ERROR("Invalid path[%s].", file_name.c_str());
      return false);

  std::ifstream file(real_path.c_str(), std::ios::binary | std::ios::ate);
  AICPU_IF_BOOL_EXEC(!file.is_open(),
      AICPU_REPORT_INNER_ERROR("open file[%s] failed.", file_name.c_str());
      return false);

  std::streamsize size = file.tellg();

  AICPU_IF_BOOL_EXEC(size <= 0, file.close();
      AICPU_REPORT_INNER_ERROR("Empty file[%s].", file_name.c_str());
      return false);

  AICPU_IF_BOOL_EXEC(size > kMaxFileSizeLimit, file.close();
      AICPU_REPORT_INNER_ERROR("File[%s] size[%ld] is out of limit[%d].",
          file_name.c_str(), size, kMaxFileSizeLimit);
      return false);

  file.seekg(0, std::ios::beg);

  buffer.resize(size);
  file.read(&buffer[0], size);
  file.close();
  AICPUE_LOGI("Binary file size is[%ld]", size);
  return true;
}

bool ValidateStr(const std::string &str, const std::string &mode) {
  regex_t reg;
  int cflags = REG_EXTENDED | REG_NOSUB;
  int ret = regcomp(&reg, mode.c_str(), cflags);
  if (ret != 0) {
    return false;
  }

  ret = regexec(&reg, str.c_str(), 0, nullptr, 0);
  if (ret != 0) {
    regfree(&reg);
    return false;
  }

  regfree(&reg);
  return true;
}

}  // namespace aicpu
